package oceanlife.pathfinding;



import java.awt.geom.GeneralPath;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class GraphBuilder {
    private Obstacles obstacles;
	public DelaunayTriangleTranslator trans;
    private PointGraph fineGraph;
    private PointGraph coarseGraph;
    private double xSize;
    private double ySize;

    public Obstacles getObstacles() {
		return obstacles;
	}
    public PointGraph getFineGraph() {
		return fineGraph;
	}
    public PointGraph getCoarseGraph() {
		return coarseGraph;
	}

	public GraphBuilder(Obstacles obs, double xSizeInc, double ySizeInc){
        obstacles = obs;
        trans = new DelaunayTriangleTranslator(obs);
        trans.makeDelaunayTriangles();
        xSize = xSizeInc;
        ySize = ySizeInc;
    }
    
    public DelaunayTriangleTranslator getDelaunayTriangleTranslator(){
    	return trans;
    }
    
	public void makeGraph(ArrayList<Line2> lines) {
	    PointGraph graph1 = new PointGraph();
		for(Line2 ent : lines){
			graph1.add(ent.getStart());
			graph1.add(ent.getEnd());
			graph1.addEdge(ent.getStart(), ent.getEnd(), new Edge(new Pnt[]{ent.getStart(),ent.getEnd()}));
		}
	    //graph1 = cleanGraph(graph1);
	    //coridorGraph(graph1,obstacles);
		//PointGraph graph2 = coarseGraph(new PointGraph(graph1));
	    //PointGraph graph3 = cleanGraph(graph2);
		
	    fineGraph = graph1;
	    //coarseGraph = graph2;
	}

	
	private void coridorGraph(PointGraph graph, Obstacles obstacles2) {
		for(Pnt nodeA : graph.getTheNeighbors().keySet()){
			for(Pnt nodeB : graph.getTheNeighbors().get(nodeA).keySet()){
				Edge edge = graph.getTheNeighbors().get(nodeA).get(nodeB);
				Pnt fp = edge.getFirstPoint();
				Pnt lp = edge.getLastPoint();
				Pnt middle = fp.getMiddle(lp);
				double minDist = Double.MAX_VALUE;
				for(int i = 0;i < obstacles.getObstacleCount();i++){
					Pnt pnt = obstacles.getClosestPoint(i, middle);
					if(pnt.distanceTo(middle) < minDist){
						minDist = pnt.distanceTo(middle);
					}
				}
				edge.setMinimumWidth(minDist);
			}
		}
	}
	
	
	
	public ArrayList<Line2> createPathLines(){
	    HashSet<Pnt> done = new HashSet<Pnt>(DelaunayTriangleTranslator.initialTriangle);
	    ArrayList<Line2> lines = new ArrayList<Line2>();
	    for (Triangle triangle : trans.getDT()){
	        for (Pnt site: triangle) {
	            if (done.contains(site)) continue;
	            done.add(site);
	            List<Triangle> list = trans.getDT().surroundingTriangles(site, triangle);
	            Pnt[] vertices = new Pnt[list.size()];
	            ArrayList<Pnt> potLines = new ArrayList<Pnt>();
	            int i = 0;
	            Pnt first = null;
	            for (Triangle tri: list){
	                vertices[i++] = tri.getCircumcenter();
	
	                if(obstacles.getIndex(tri) == -1){ 
	            	//if(notInObs(tri.getCircumcenter())){
	                    potLines.add(tri.getCircumcenter());
	                    if(i == 1){
	                    	first = tri.getCircumcenter();
	                    }
	            	} else {
	            		potLines.clear();  
	            	}
	            	Line2 line = null;
	                if(potLines.size() == 2){
	                	line = new Line2(potLines.get(0),potLines.get(1));
	                    potLines.remove(0);
	                }
	                if(first != null && i == list.size() && potLines.size() == 1){
	                	line = new Line2(potLines.get(0),first);
	                }
	                if(line != null && !lines.contains(line)){
	                	lines.add(line);
	                }
	            }
	            //draw(vertices, withFill? getColor(site) : null);
	            //if (withSites) draw(site);
	        }
	    }
	    return lines;
    }
	//TODO rounding errors. polygon and point are int / triangle and Pnt are double
	private boolean notInObs(Pnt circumcenter) {
		for(Triangle ob : obstacles.getObstacles()){
			if(ob.toGeneralPath().contains(circumcenter.coord(0),circumcenter.coord(1))){
				return false;
			}
		}
		for(Pnt[] pnts : obstacles.getInputObstacles()){
			for(int i = 0; i < pnts.length;i++){
				int j = i+1;
				if(j == pnts.length){
					j = 0;
				}
				if(circumcenter.isOnLine(new Pnt(pnts[i].coord(0),pnts[i].coord(1)),new Pnt(pnts[j].coord(0),pnts[j].coord(1)))){
					//return false;
				}
			}
		}
		return true;
	}
	private PointGraph coarseGraph(PointGraph cleanGraph) {
		//make coarse graph
		while(cleanGraph.nodeWith2Branches()){
			for(Pnt nodeA : cleanGraph.getTheNeighbors().keySet()){
				Pnt[] keySet = cleanGraph.getTheNeighbors().get(nodeA).keySet().toArray(new Pnt[0]);
				if(keySet.length == 2){
					Pnt pnt0 = keySet[0]; 
					Pnt pnt1 = keySet[1]; 
					Edge[] valueSet = (Edge[]) cleanGraph.getTheNeighbors().get(nodeA).values().toArray(new Edge[0]);
					if(valueSet.length != 2){
						System.out.println("GA NIE GOE!");
					}
					Edge newEdge = valueSet[0].combine(valueSet[1]);
					cleanGraph.remove(nodeA);
					cleanGraph.addEdge(pnt0, pnt1, newEdge);
					break;
				}
			}
		}
		return cleanGraph;
	}
	private PointGraph cleanGraph(PointGraph fineGraph) {
		//clean up
		System.out.println("1");
		//remove edges outside drawbox, but keep edges with one point in drawbox
		for(Pnt key : fineGraph.getTheNeighbors().keySet()){
			Pnt[] keySet = fineGraph.getTheNeighbors().get(key).keySet().toArray(new Pnt[0]);
			for(int i = 0; i < keySet.length;i++){
				if(farAway(key) && farAway(keySet[i])){
					fineGraph.remove(key,keySet[i]);
				}
			}
		}
		System.out.println("2");
		
		//remove empty nodes
		ArrayList<Pnt> emptyNodes = new ArrayList<Pnt>();
		for(Pnt node : fineGraph.getTheNeighbors().keySet()){
			if(fineGraph.getTheNeighbors().get(node).size() == 0){
				emptyNodes.add(node);
			}
		}
		for(Pnt emptyNode : emptyNodes){
			fineGraph.remove(emptyNode);
		}
		
		System.out.println("3");
		//remove edges series that go outside of drawbox but have no connection to main graph
		//each point outside drawbox should only have one neighbour before this filter applies
		
		Pnt[] nodes = fineGraph.getTheNeighbors().keySet().toArray(new Pnt[0]);
		for(int i = 0; i < nodes.length;i++){
			if(fineGraph.getTheNeighbors().get(nodes[i]) == null){
				continue;
			}
			Pnt[] nodeSet = fineGraph.getTheNeighbors().get(nodes[i]).keySet().toArray(new Pnt[0]);
			if(farAway(nodes[i]) && !hasConnectionPoint(fineGraph,nodes[i],nodeSet[0])){
				destroyChain(fineGraph,nodes[i],nodeSet[0]);
			}
		}
		System.out.println("4");
		
		return fineGraph;
	}
	private void destroyChain(PointGraph graph, Pnt nodeA, Pnt nodeB) {
		System.out.println("destroy "+nodeA.coord(0)+"/"+nodeA.coord(1)+" "+nodeB.coord(0)+"/"+nodeB.coord(1));
		Pnt current = nodeA;
		Pnt next = nodeB;
		while(graph.getTheNeighbors().get(next).keySet().toArray(new Pnt[0]).length > 1){
			System.out.println("        "+current.coord(0)+"/"+current.coord(1));
			graph.remove(current);
			//Pnt[] test = graph.getTheNeighbors().get(next).keySet().toArray(new Pnt[0]);
			current = next;
			next = graph.getTheNeighbors().get(next).keySet().toArray(new Pnt[0])[0];
		}
		System.out.println("        "+next.coord(0)+"/"+next.coord(1));
		graph.remove(next);
		
		
	}
	private boolean hasConnectionPoint(PointGraph graph, Pnt nodeA, Pnt nodeB) {
		// direction of search is behind nodeB from nodeA's perspective
		System.out.println("chain check "+nodeA.coord(0)+"/"+nodeA.coord(1)+" "+nodeB.coord(0)+"/"+nodeB.coord(1));
		boolean nothingFound = true;
		Pnt prev = nodeA;
		Pnt current = nodeB;
		while(nothingFound){
			if(farAway(current)){
				return false;
			}
			Pnt[] keySet = graph.getTheNeighbors().get(current).keySet().toArray(new Pnt[0]);
			System.out.println("            "+current.coord(0)+"/"+current.coord(1)+"  "+keySet.length);
			if(keySet.length == 1){
				return false;
			} else if(keySet.length == 2){
				Pnt temp = current;
				if(keySet[0].equals(prev)){
					current = keySet[1];
				} else {
					current = keySet[0];
				}
				prev = temp;
			} else {
				return true;
			}
		}
		return false;
	}
	private boolean farAway(Pnt key2) {
		if(key2.coord(0) < 0 || key2.coord(0) > xSize || key2.coord(1) < 0 || key2.coord(1) > ySize){
			return true;
		}
		return false;
	}
	
	public static void main(String[] args) {
		Pnt[] pnts = new Pnt[3];
		pnts[0] = new Pnt(-1.0,0.0);
		pnts[1] = new Pnt(-2.0,0.5);
		pnts[2] = new Pnt(-1.5,0.25);
		//Triangle tr = new Triangle(pnts);
		//GeneralPath gp = new GeneralPath();
		//gp.moveTo(pnts[0].coord(0), pnts[0].coord(1));
		//gp.lineTo(pnts[1].coord(0), pnts[1].coord(1));
		//gp.lineTo(pnts[2].coord(0), pnts[2].coord(1));
		//gp.closePath();
		
		
		//Pnt whatever = pnts[2].isOn(new Pnt[]{pnts[0],pnts[1]});

		Line2 whatever = new Line2(pnts[0],pnts[1]);
		
		
		System.out.println(pnts[2].isOnLine(whatever));
	}

}
